package xj.graph2d;

import static xj.util.xml.XMLUtil.getXMLAttributeString;

import java.awt.Graphics2D;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;

import xj.lang.CodeWriter;
import xj.util.gui.Point2DFloat;

public class Arrow 
	extends ArrowAttr 
	implements Cloneable, java.io.Serializable {

  /**
   * 
   */
  private static final long serialVersionUID = 1880576745012791421L;

  public Arrow() {
    pos = new Point2DFloat();
    at = new AffineTransform();
  }

  public Arrow(ArrowAttr attr) {
    pos = new Point2DFloat();
    at = new AffineTransform();
    if (attr != null) {
      this.type = attr.getType();
      this.width = attr.getWidth();
      this.height = attr.getHeight();
    }
  }

  public Arrow(ArrowType type, float width, float height) {
    pos = new Point2DFloat();
    at = new AffineTransform();
    this.type = type;
    this.width = width;
    this.height = height;
  }

  @Override
  public Object clone() {
    Arrow arrow = new Arrow();
    arrow.setPosition((float) pos.getX(), (float) pos.getY());
    arrow.setAngle(angle);
    arrow.type = type;
    arrow.width = width;
    arrow.height = height;
    return arrow;
  }

  public void draw(Graphics2D g2) {
    if (type != ArrowType.NoArrow) {
      AffineTransform oldTx = g2.getTransform();
      Stroke oldStroke = g2.getStroke();
      Stroke arrowStroke = null;
      StrokeMap smap = StrokeMap.getInstance();
      if (owner != null) {
	DrawAttr attr = owner.getDrawAttr();
	if (attr != null) {
	  arrowStroke = smap.findStroke(attr.getPenSize(), 
					attr.getLineCap(),
					attr.getLineJoin());
	}
      }
      if (arrowStroke == null) {
	arrowStroke = smap.findStroke(1);
      }
      g2.setStroke(arrowStroke);
      g2.translate(pos.getX(), pos.getY());
      g2.transform(at);
      float w, h;
      GeneralPath path = new GeneralPath();
      switch (type) {
      case LineArrow:
	path.moveTo(width, height / 2);
	path.lineTo(0, 0);
	path.lineTo(width, -height / 2);
	g2.draw(path);
	break;

      case HollowTriangle:
      case FilledTriangle:
	path.moveTo(width, 0);
	path.lineTo(width, height / 2);
	path.lineTo(0, 0);
	path.lineTo(width, -height / 2);
	path.lineTo(width, 0);
	if (type == ArrowType.HollowTriangle) {
	  g2.draw(path);
	} else {
	  g2.fill(path);
	}
	break;

      case HollowDiamond:
      case FilledDiamond:
	path.moveTo(width, 0);
	path.lineTo(width / 2, height / 2);
	path.lineTo(0, 0);
	path.lineTo(width / 2, -height / 2);
	path.lineTo(width, 0);
	if (type == ArrowType.HollowDiamond) {
	  g2.draw(path);
	} else {
	  g2.fill(path);
	}
	break;

      case HollowDot:
	g2.draw(new Ellipse2D.Float(0, -height / 2, width, height));
	break;

      case FilledDot:
	g2.fill(new Ellipse2D.Float(0, -height / 2, width, height));
	break;

      case HalfCircle:
	g2.draw(new Arc2D.Float(-width / 2, -height / 2, width, height, -90,
	    180, Arc2D.OPEN));
	break;

      case FilledHalfMoon:
	g2.fill(new Arc2D.Float(-width / 2, -height / 2, width, height, -90,
	    180, Arc2D.PIE));
	break;

      case HollowHalfMoon:
	g2.draw(new Arc2D.Float(-width / 2, -height / 2, width, height, -90,
	    180, Arc2D.PIE));
	break;

      case FilledHalfMoon2:
	g2.fill(new Arc2D.Float(0, -height / 2, width, height, 90, 180,
	    Arc2D.PIE));
	break;

      case HollowHalfMoon2:
	g2.draw(new Arc2D.Float(0, -height / 2, width, height, 90, 180,
	    Arc2D.PIE));
	break;

      case CrossDot:
	g2.draw(new Ellipse2D.Float(0, -height / 2, width, height));
	path.moveTo(width, 0);
	path.lineTo(0, 0);
	path.moveTo(width / 2, height / 2);
	path.lineTo(width / 2, -height / 2);
	g2.draw(path);
	break;

      case XArrow:
	g2.draw(new Line2D.Float(0, -height / 2, width, height / 2));
	g2.draw(new Line2D.Float(0, height / 2, width, -height / 2));
	break;

      case HollowDiamondArrow:
      case FilledDiamondArrow:
	w = width;
	h = height;
	path.moveTo(w, 0);
	path.lineTo(w / 2, h / 2);
	path.lineTo(0, 0);
	path.lineTo(w / 2, -h / 2);
	path.lineTo(w, 0);
	if (type == ArrowType.HollowDiamondArrow) {
	  g2.draw(path);
	} else {
	  g2.fill(path);
	}
	g2.draw(new Line2D.Float(w, 0, w * 2, h / 2));
	g2.draw(new Line2D.Float(w, 0, w * 2, -h / 2));
	break;

      case HollowDiamondX:
      case FilledDiamondX:
	w = width;
	h = height;
	path.moveTo(w, 0);
	path.lineTo(w / 2, h / 2);
	path.lineTo(0, 0);
	path.lineTo(w / 2, -h / 2);
	path.lineTo(w, 0);
	if (type == ArrowType.HollowDiamondX) {
	  g2.draw(path);
	} else {
	  g2.fill(path);
	}
	g2.draw(new Line2D.Float(w, -h / 2, w * 2, h / 2));
	g2.draw(new Line2D.Float(w, h / 2, w * 2, -h / 2));
	break;
      case NoArrow:
      default:
      }

      g2.setStroke(oldStroke);
      g2.setTransform(oldTx);
    }
  }

  public DrawShape getOwner() {
    return owner;
  }

  public void setOwner(DrawShape owner) {
    this.owner = owner;
  }

  public Point2D getPosition() {
    return pos;
  }

  public void setPosition(float x, float y) {
    pos.setLocation(x, y);
  }

  public float getAngle() {
    return angle;
  }

  public void setAngle(float angle) {
    this.angle = angle;
    at.setToRotation(angle);
  }

  @Override
  public ArrowType getType() {
    return type;
  }

  @Override
  public void setType(ArrowType type) {
    this.type = type;
  }

  public Point2D getTailPosition() {
    Point2D result = pos;
    float[] src = null;
    switch (type) {
    case NoArrow:
    case LineArrow:
      break;

    case HollowTriangle:
    case FilledTriangle:
    case HollowDiamond:
    case FilledDiamond:
    case HollowDot:
    case FilledDot:
    case CrossDot:
      src = new float[] { width, 0 };
      break;

    case HalfCircle:
    case FilledHalfMoon:
    case HollowHalfMoon:
    case FilledHalfMoon2:
    case HollowHalfMoon2:
      src = new float[] { width / 2, 0 };
      break;

    case XArrow:
      break;

    case HollowDiamondArrow:
    case FilledDiamondArrow:
    case HollowDiamondX:
    case FilledDiamondX:
      src = new float[] { width, 0 };
    }
    if (src != null) {
      float[] dst = new float[2];
      at.transform(src, 0, dst, 0, 1);
      result = new Point2DFloat(dst[0] + (float) pos.getX(), dst[1]
	  + (float) pos.getY());
    }
    return result;
  }

  public void writeXML(CodeWriter out, String namespacePrefix) {
    if (out != null) {
      String nodename = DrawShapeBase.getPrefixedName("Arrow", namespacePrefix);
      out.println("<" + nodename + ' ' + getXMLAttributeString("type", type)
	  + ' ' + getXMLAttributeString("x", pos.getX()) + ' '
	  + getXMLAttributeString("y", pos.getY()) + ' '
	  + getXMLAttributeString("w", width) + ' '
	  + getXMLAttributeString("h", height) + ' '
	  + getXMLAttributeString("a", angle) + "/>");
    }
  }

  public void setAttrFromXML(org.w3c.dom.Element node) {
    setAttrFromXML(node, null);
  }

  public void setAttrFromXML(org.w3c.dom.Element node, String namespaceURI) {
    if (node != null && node.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
      org.w3c.dom.Element e = node;
      String name = e.getLocalName();
      String uri = e.getNamespaceURI();
      if ((namespaceURI == null || namespaceURI.equals(uri))
	  && "Arrow".equals(name)) {
	String ts, xs, ys, ws, hs, as;
	float x, y, w, h, a;
	ts = e.getAttribute("type");
	xs = e.getAttribute("x");
	ys = e.getAttribute("y");
	ws = e.getAttribute("w");
	hs = e.getAttribute("h");
	as = e.getAttribute("a");
	try {
	  type = Enum.valueOf(ArrowType.class, ts);
	} catch (IllegalArgumentException ex) {
	  type = ArrowType.NoArrow;
	}
	try {
	  x = Float.parseFloat(xs);
	  y = Float.parseFloat(ys);
	  w = Float.parseFloat(ws);
	  h = Float.parseFloat(hs);
	  a = Float.parseFloat(as);
	  setPosition(x, y);
	  setAngle(a);
	  width = w;
	  height = h;
	} catch (NumberFormatException ex) {
	}
      }
    }
  }

  protected DrawShape owner;

  protected Point2D pos; // the tip

  protected float angle;

  protected AffineTransform at = null;

}
